利用PyTorch构造简单卷积神经网络

您所在的位置:网站首页 神经网络 weight bias 利用PyTorch构造简单卷积神经网络

利用PyTorch构造简单卷积神经网络

#利用PyTorch构造简单卷积神经网络| 来源: 网络整理| 查看: 265

【PyTorch深度学习实战】Chapter 8

在上一章中,我们利用了线性变换构建了一个简单的神经网络,区分鸟和飞机。输入特征是图像的每一个像素点,这样的做法丧失了图像中包含的位置信息,也即不具备平移不变性。在图像处理中,我们就有一个现成的、局部的、平移不变的线性操作:卷积,更准确的说是离散卷积,即核函数与输入中的每个邻域的标量积

在这一章,我们会介绍卷积、池化、nn.Sequential、nn.Module等构建模型的模块,以及讨论模型的宽度、正则化手段和深度。

https://indiantechwarrior.com/convolution-layers-in-convolutional-neural-network/

上面这个动图很好,卷积的具体原理不再赘述,继续回到PyTorch。

注意,就像nn.Linear权重矩阵中的元素一样,我们事先是不知道卷积核权重矩阵中的元素的值的,他们是随机初始化的,在反向传播中更新。

torch.nn模块提供一维、二维、三维的卷积,其中nn.Conv1d用于时间序列,nn.Conv2d用于图像,nn.Conv3d用于体数据和视频。另外书在这里还写到:由于我们是随机对卷积核进行初始化的,因此我们得到的一些特征,即使经过训练后,也是无用的。(译文翻译的晦涩,有兴趣可见彩票假设:The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks)

卷积核各个方向的大小都相同是很寻常的,即我们经常用3x3、5x5这样的卷积核。特别的,例如,当为二维卷积核指定kernel_size为3时,Python是以tuple(3,3)提供给Conv2d的,即3x3,对于三维卷积,同理指的是3x3x3:

conv = nn.Conv2d(3, 16, kernel_size=3) # kernel_size=(3,3) conv # Conv2d(3, 16, kernel_size=(3, 3), stride=(1, 1)) conv.weight.shape, conv.bias.shape # (torch.Size([16, 3, 3, 3]), torch.Size([16]))

conv的权重和偏置的shape符合理论大小(out_channel×in_channel×kernel_size)。如果我们想给conv输入一张图像,我们首先得利用unsqueeze给图像添加第0维度,因为nn.Conv2d()期望输入一个BCHW的张量:

img, _ = cifar2[0] output = conv(img.unsqueeze(0)) img.unsqueeze(0).shape, output.shape # (torch.Size([1, 3, 32, 32]), torch.Size([1, 16, 30, 30]))

注意到输出的大小变成30了,而CIFAR10数据集每张图片像素大小为32,在这个过程中我们丢失了一些像素信息。这是因为我们没有在卷积里设置padding即填充,卷积核将在图像中滑动,最终得到的图像大小是width-kernel_size+1。但是我们可以在图像周围打上填充,例如0填充,使得卷积后得到的图像与原图像一样大小:

conv = nn.Conv2d(3, 16, kernel_size=3, padding=1) output = conv(img.unsqueeze(0)) img.unsqueeze(0).shape, output.shape # (torch.Size([1, 3, 32, 32]), torch.Size([1, 16, 32, 32]))

另外,书中提到对于偶数大小的卷积核,我们需要在左右以及上下填充不同的数字,PyTorch没有在卷积中有相关的操作,但是仍然可以通过torch.nn.functional.pad()函数实现。最好使用奇数大小的卷积核,偶数大小的卷积核有些奇怪。填充padding主要目的就是为了控制卷积后的特征图的大小,也方便一些网络中实现不同层级特征图之间的相加或相减,毕竟大小相同是前提。

卷积核还有作用就是检测特征,我们来看一下如果手动设置卷积核中权重的值,会产生什么效果:

with torch.no_grad(): conv.bias.zero_() conv.weight.fill_(1.0 / 9.0) output = conv(img.unsqueeze(0)) plt.imshow(output[0,0].detach(), cmap='gray') plt.show()

上面的代码实现了将卷积核权重的值都设置为1/9,本质就是一个均值滤波器,看一下输出图像的效果:

均值滤波器会模糊图像

通过深度学习,卷积核中的权重将从数据中得以被估计。卷积神经网络的工作原理就是把一个多通道图像转换成另一种多通道图像,其中不同的通道对应不同的特征,例如一个通道检测图像平均值,一个通道检测初值边缘,一个通道检测小鸟的头部。

我们现在完成了全连接到卷积的过渡了,实现了局部性和平移不变性,并且还建议用小卷积核,以实现峰值局部性。但是,我们如何观察到整体情况呢?因为不是所有我们要关注的物体都在3x3像素和5x5像素的卷积核之内的,即我们的网络如何看到更大范围内的图像呢?(当然不是利用32x32的卷积核...)

解决办法就是在一个卷积后再来一个卷积,在连续卷积的过程中实现对图像的下采样(池化)。池化策略有很多种,最大池化用的比较多,即在滑动窗口内保留最大的像素值到下一层。最大池化可以确保所发现到的特征在下采样之后仍然存在,而丢弃一些不是很重要的信息。

PyTorch的最大池化由nn.MaxPool2d提供,如果我们想通过最大池化将图像大小变为原来的一半,我们让核大小为2:

pool = nn.MaxPool2d(2) output = pool(img.unsqueeze(0)) img.unsqueeze(0).shape, output.shape # (torch.Size([1, 3, 32, 32]), torch.Size([1, 3, 16, 16]))

在这样的卷积-池化的下采样过程中,后续的特征受到前序的特征影响,输出像素的感受野较大,模型可以看到更大的物体。

下面,我们就可以把卷积和池化整合在一起,代替上一章节的全连接网络了:

model = nn.Sequential( nn.Conv2d(3, 16, kernel_size=3, padding=1), nn.Tanh(), nn.MaxPool2d(2), nn.Conv2d(16, 8, kernel_size=3, padding=1), nn.Tanh(), nn.MaxPool2d(2), nn.Flatten(), # 将特征图打平 nn.Linear(8*8*8, 32), nn.Tanh(), nn.Linear(32, 2) ) numel_list = [p.numel() for p in model.parameters()] sum(numel_list), numel_list # (18090, [432, 16, 1152, 8, 16384, 32, 64, 2])

现在的参数量就合理的多了。

nn.Sequential()适合构造小模型,但当我们的网络中有一些比较大的、可以复用的结构时,如何减少我们的代码量呢?通过实现自己的nn.Module子类。

为了子类化nn.Module,我们至少需要定义一个forward方法,该方法接受模块的输入并返回输出,亦即我们定义模块的计算顺序的地方。通常情况下,计算会涉及到其他模块,例如卷积,为了能够利用这些模块,我们会提前在构造函数__init__()中定义他们,并将他们分配给self以便在forward()方法中使用。同时,他们将在模块的整个生命周期中保存他们的参数。注意,在使用前不要忘记super().__init__()。(就是继承父类的构造函数)

举个例子,将我们上面的网络作为一个nn.Module()的子类:

class Net(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) self.act1 = nn.Tanh() self.pool1 = nn.MaxPool2d(2) self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1) self.act2 = nn.Tanh() self.pool2 = nn.MaxPool2d(2) self.fc1 = nn.Linear(8 * 8 * 8, 32) self.act3 = nn.Tanh() self.fc2 = nn.Linear(32, 2) def forward(self, x): out = self.pool1(self.act1(self.conv1(x))) out = self.pool2(self.act2(self.conv2(out))) out = out.view(-1, 8*8*8) out = self.act3(self.fc1(out)) out = self.fc2(out) return out

Net类相当于我们之前用Sequential实现的模型,但是我们显式编写了forward()方法,我们可以直接调用view()将其转换为一个BxN的向量。注意这里是-1,因为原则上我们不知道批次中有多少个样本。

上述的代码属于模块化API的思想,即在构造函数里也写好了诸如Tanh()、MaxPool2d()这样的小模块,但事实上,这些语句完全可以只写在forward()里,这就是函数式API的思想,他们之间没有任何区别:

import torch.nn.functional as F class Net(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1) self.fc1 = nn.Linear(8 * 8 * 8, 32) self.fc2 = nn.Linear(32, 2) def forward(self, x): out = F.max_pool2d(torch.tanh(self.conv1(x)), 2) out = F.max_pool2d(torch.tanh(self.conv2(out)), 2) out = out.view(-1, 8*8*8) out = torch.tanh(self.fc1(out)) out = self.fc2(out) return out

这样代码量就会减少一点。torch.nn.functional里提供了很多与nn中模块类似的函数,但是他们不像模块那样处理实参和存储参数,而是只将输入和自己定义好的参数作为函数调用的实参。(具体见:torch.nn库和torch.nn.functional库_jk英菲尼迪的博客-CSDN博客_torch.nn.functional)例如,与nn.Linear()对应的是nn.functional.linear(),需要传入input,weight,bias,weight和bias都要自己定义好,然后传入,透明度比较高,只是看起来麻烦一点。所以,对于没有参数的操作,可以用nn.functional。

另外,值得注意的是,虽然tanh这样的通用函数在torch.nn.functional中仍然存在,但是不建议使用这个,而是建议使用torch顶级命名空间的函数(torch.tanh())。

总的来说,使用函数式API(nn.functional)还是模块化API(nn)看你个人喜好。当网络的一部分比较简单,以至于我们想使用nn.Sequential()时,建议用模块化API,方便快速,对于我们自己编写的forward()中,不需要参数的语句时,用函数式API可能更自然。

现在实例化一下我们的模型,看它能否正常工作:

model = Net() model(img.unsqueeze(0)) # tensor([[-0.0369, -0.1894]], grad_fn=)

能正确输出两个数字。

现在我们已经完成了模型的基本构建,后面要做的就是训练我们的convnet。训练循环主要是2个嵌套的循环,一是跨迭代周期(for epoch in xxx)的外部循环,还有一个是从DataLoader采样生成批次的内部循环,在训练循环中,我们主要做的是这个事情:提供输入给模型→计算loss→旧梯度清0→调用loss.backward()计算loss相对所有参数的梯度,即反向传播→参数更新

import datetime # 方便计时 def training_loop(n_epochs, optimizer, model, loss_fn, train_loader): for epoch in range(1, n_epochs + 1): loss_train = 0 for imgs, labels in train_loader: # batch outputs = model(imgs) loss = loss_fn(outputs, labels) optimizer.zero_grad() # 梯度是累积的,要清除上一个batch的梯度 loss.backward() # 反向传播 optimizer.step() # 更新模型 loss_train += loss.item() # 通过item来获得loss的具体数值,不然的话loss变量本身是Variable类型,显存会炸 if epoch == 1 or epoch % 10 == 0: print(f"{datetime.datetime.now()} Epoch {epoch} Training Loss {loss_train / len(train_loader) :.3f} ")

开始训练:

import torch.optim as optim train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=True) model = Net() optimizer = optim.SGD(model.parameters(), lr=1e-2) loss_fn = nn.CrossEntropyLoss() # training training_loop(n_epochs=100, optimizer=optimizer, model=model, loss_fn=loss_fn, train_loader=train_loader) ''' 2022-10-16 18:04:42.868908 Epoch 1 Training Loss 0.565 2022-10-16 18:05:10.154757 Epoch 10 Training Loss 0.336 2022-10-16 18:05:41.588419 Epoch 20 Training Loss 0.300 2022-10-16 18:06:12.986913 Epoch 30 Training Loss 0.277 2022-10-16 18:06:45.657037 Epoch 40 Training Loss 0.254 2022-10-16 18:07:19.377885 Epoch 50 Training Loss 0.236 2022-10-16 18:07:55.171301 Epoch 60 Training Loss 0.222 2022-10-16 18:08:32.253506 Epoch 70 Training Loss 0.205 2022-10-16 18:09:11.560051 Epoch 80 Training Loss 0.190 2022-10-16 18:09:54.512876 Epoch 90 Training Loss 0.179 2022-10-16 18:10:34.188558 Epoch 100 Training Loss 0.162 '''

我们看到loss确实在下降,证明网络训练是有效的。但是我们不知道其具体的在训练集/验证集上的分类的表现,所以:

train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=False) val_loader = torch.utils.data.DataLoader(cifar2_val, batch_size=64, shuffle=False) def validate(model, train_loader, val_loader): for name, loader in [("train", train_loader), ("val", val_loader)]: correct = 0 total = 0 with torch.no_grad(): # 防止梯度更新 for imgs, labels in loader: outputs = model(imgs) _, predicted = torch.max(outputs, dim=1) total += labels.shape[0] correct += int((predicted == labels).sum()) print(f"Acc {name} {correct / total : .3f}") validate(model, train_loader, val_loader) ''' Acc train 0.936 Acc val 0.894 '''

这样的结果比上一章的全连接网络好多了,且参数量更小。

下面学习一下如何保存我们的模型:

torch.save(model.state_dict(), data_path + 'birds_vs_airplanes.pt')

注意,这种保存模型的方法只是保存了所有的参数,不包含模型结构本身。因此,如果我们在其他地方部署该模型,必须先实例化好模型,再加载参数:

loaded_model = Net() loaded_model.load_state_dict(torch.load(data_path + 'birds_vs_airplanes.pt')) #

在第三章的时候,我们就提过把运算放在GPU上(Tensor.to()),会更快。令人高兴的是,nn.Module模块实现了一个将模型所有参数移动到GPU上的to()方法,当你传递一个dtype参数时使用该方法还可以强制转换类型。

Module.to和Tensor.to之间存在一些微小的区别,区别在于模块实例可否被更改。对于Module.to来说,模块实例本身的参数会均移动到其他地方,而Tensor.to方法会返回一个位于其他地方的新的张量。

如果GPU可用的话,把一切都移到GPU上是一个不错的方式,一般的,可以按下面的方式设定device:

device = (torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')) device # device(type='cuda')

接着,我们就可以通过Tensor.to方法,把张量先转移到GPU上,再进行计算(只改了两行,validate函数同理):

import datetime def training_loop(n_epochs, optimizer, model, loss_fn, train_loader): for epoch in range(1, n_epochs + 1): loss_train = 0 for imgs, labels in train_loader: # batch imgs = imgs.to(device) # to labels = labels.to(device) # to outputs = model(imgs) loss = loss_fn(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() loss_train += loss.item() if epoch == 1 or epoch % 10 == 0: print(f"{datetime.datetime.now()} Epoch {epoch} Training Loss {loss_train / len(train_loader) :.3f} ")

现在数据是在GPU上了,但是模型的参数不在,所以仍然要移动模型的参数

train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=True) model = Net().to(device) optimizer = optim.SGD(model.parameters(), lr=1e-2) loss_fn = nn.CrossEntropyLoss() # training training_loop(n_epochs=100, optimizer=optimizer, model=model, loss_fn=loss_fn, train_loader=train_loader) ''' 2022-10-16 19:32:08.329159 Epoch 1 Training Loss 0.590 2022-10-16 19:32:12.213774 Epoch 10 Training Loss 0.332 2022-10-16 19:32:16.441477 Epoch 20 Training Loss 0.293 2022-10-16 19:32:20.758944 Epoch 30 Training Loss 0.268 2022-10-16 19:32:25.294807 Epoch 40 Training Loss 0.247 2022-10-16 19:32:29.792782 Epoch 50 Training Loss 0.229 2022-10-16 19:32:34.335148 Epoch 60 Training Loss 0.212 2022-10-16 19:32:38.852072 Epoch 70 Training Loss 0.198 2022-10-16 19:32:43.501645 Epoch 80 Training Loss 0.185 2022-10-16 19:32:47.983175 Epoch 90 Training Loss 0.171 2022-10-16 19:32:52.653689 Epoch 100 Training Loss 0.156 '''

注意,如果数据的位置和模型参数的位置不一样,即一个在CPU一个在GPU,会报错"Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same",也就是PyTorch不支持GPU和CPU的混合输入。

通过结果,可以发现训练只用了40s,比上面一开始用CPU训练,快了极多倍。在加载权重的时候,会稍微复杂一点,因为PyTorch会尝试将权重加载到保存权重时的设备上,即GPU上的权重恢复到GPU上,利用map_location实现(默认就是GPU权重移到GPU,CPU权重到CPU,这样写更显式):

loaded_model = Net().to(device) loaded_model.load_state_dict(torch.load(data_path + 'birds_vs_airplanes.pt', map_location=device)) #

下面主要讲的是模型的宽度、泛化和深度

一.宽度

简单来讲,我们可以通过增加通道数和特征数来增加模型的容量,容量越大,模型过拟合的可能性也越高。对抗过拟合最好的办法是增加样本数量,但是我们在没有新数据的情况下,也可以通过人工方法改进模型,控制过拟合。

二.泛化

当过拟合时,模型的泛化能力较差。为了控制过拟合,我们引入一些正则化的方法。

1.首先是L2正则化,思想非常简单:是模型中所有权重的平方和。其他的,还有L1正则化,即是模型中所有权重的绝对值之和。它们都通过一个小因子进行缩放,也是一个超参数

L2正则化也称为权重衰减,叫这个名字是考虑到SGD和反向传播。L2正则化对参数w_i的负梯度为-2×lambda×w_i,lambda就是超参数。在损失函数中加入L2正则化之后,相当于在每一个优化步骤中,将权重都按一定的比例递减,所以叫权重衰减。注意,权重衰减适用于所有参数,如偏置。

在PyTorch中,我们手动计算L2添加到损失函数中:

import datetime def training_loop(n_epochs, optimizer, model, loss_fn, train_loader): for epoch in range(1, n_epochs + 1): loss_train = 0 for imgs, labels in train_loader: # batch imgs = imgs.to(device) # to labels = labels.to(device) # to outputs = model(imgs) loss = loss_fn(outputs, labels) l2_lambda = 0.001 l2_norm = sum(p.pow(2.0).sum() for p in model.parameters()) loss = loss + l2_lambda * l2_norm optimizer.zero_grad() loss.backward() optimizer.step() loss_train += loss.item() if epoch == 1 or epoch % 10 == 0: print(f"{datetime.datetime.now()} Epoch {epoch} Training Loss {loss_train / len(train_loader) :.3f} ")

但是,PyTorch的SGD优化器已经有一个weight_decay的参数了,该参数对应的就是2×lambda,它与我们上面的手动计算完全一样,所以我们没必要自己在loss里添加L2范数了。

2.Dropout

Dropout的思想很简单:将网络每轮训练迭代中的神经元随机部分失活(归零)。可以这样认为:Dropout使得模型中的神经元在过拟合过程中协调记忆的机会减少,也可以认为是Dropout在整个网络中干扰了模型生成的特征,产生一种类似于增强的效果。

在PyTorch中,我们可以通过在激活函数和后面的线性/卷积块之间添加nn.Dropout模块,我们需要为其指定归零的概率。对于卷积,我们要用专门的nn.Dropout2d:

import torch.nn.functional as F class Net(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) self.conv1_dropout = nn.Dropout2d(p=0.4) self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1) self.conv2_dropout = nn.Dropout2d(p=0.4) self.fc1 = nn.Linear(8 * 8 * 8, 32) self.fc2 = nn.Linear(32, 2) def forward(self, x): out = F.max_pool2d(torch.tanh(self.conv1(x)), 2) out = self.conv1_dropout(out) out = F.max_pool2d(torch.tanh(self.conv2(out)), 2) out = self.conv2_dropout(out) out = out.view(-1, 8*8*8) out = torch.tanh(self.fc1(out)) out = self.fc2(out) return out

注意,Dropout一般是在训练过程中使用的,但是在验证和测试的时候,不能再使用Dropout,不然会导致结果的不稳定性。或者等效的给其分配一个为0的概率。回想一下,PyTorch允许我们在任意nn.Model子类上通过调用model.train()model.eval()来实现2种模式的切换。

3.批量归一化

批量归一化BatchNormlization,其主要思想是调整输入的分布,这有助于避免激活函数的输入过多的进入函数的饱和部分,导致梯度消失,减慢训练速度。批量归一化是使用中间位置收集的小批量样本的平均值和标准差来对中间输入进行移位和缩放,使用批量归一化或减轻对Dropout的依赖。

PyTorch提供了nn.BatchNorm1d等来实现批量归一化,使用哪种取决于输入数据的维度。由于批量归一化的目的是重新调整输入,因此其位置是在线性变换(例如卷积)和激活函数之间:

import torch.nn.functional as F class Net(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) self.conv1_batchnorm = nn.BatchNorm2d(num_features=16) self.conv2 = nn.Conv2d(16, 8, kernel_size=3, padding=1) self.conv2_batchnorm = nn.BatchNorm2d(num_features=8) self.fc1 = nn.Linear(8 * 8 * 8, 32) self.fc2 = nn.Linear(32, 2) def forward(self, x): out = self.conv1_batchnorm(self.conv1(x)) out = F.max_pool2d(torch.tanh(out), 2) out = self.conv2_batchnorm(self.conv2(out)) out = F.max_pool2d(torch.tanh(out), 2) out = out.view(-1, 8*8*8) out = torch.tanh(self.fc1(out)) out = self.fc2(out) return out

和Dropout一样,批量归一化在训练和推理过程中表现不同的行为。实际上,在推理过程中,我们希望避免特定输入的输出依赖于我们提供给模型的其他输入的统计数据的情况

三、深度

一般来说,随着深度的增加,网络所能近似函数的复杂性也会增加。

1.skip connection

具体原理不再赘述,具体看PyTorch的实现,就是将输入与输出相加:

import torch.nn.functional as F class Net(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) self.conv3 = nn.Conv2d(32, 8, kernel_size=3, padding=1) self.fc1 = nn.Linear(8 * 8 * 8, 32) self.fc2 = nn.Linear(32, 2) def forward(self, x): out = F.max_pool2d(torch.tanh(self.conv1(x)), 2) out = F.max_pool2d(torch.tanh(self.conv2(out)), 2) out1 = out out = F.max_pool2d(torch.tanh(self.conv3(out)) + out1, 2) out = out.view(-1, 8*8*8) out = torch.tanh(self.fc1(out)) out = self.fc2(out) return out

2.如何使用PyTorch构建非常深的模型

我们当然不可能在__init__()把上百层网络的层都写好,而是会利用for循环动态构建网络,所以在模型实现前,要先找到模型的可复用结构,对于ResNet来说就是ResBlock

class ResBlock(nn.Module): def __init__(self, n_chans): super().__init__() self.conv = nn.Conv2d(n_chans, n_chans, kernel_size=3, padding=1, bias=False) self.batch_norm = nn.BatchNorm2d(num_features=n_chans) torch.nn.init.kaiming_normal_(self.conv.weight, nonlinearity='relu') # 初始化的一种 torch.nn.init.constant_(self.batch_norm.weight, 0.5) torch.nn.init.zeros_(self.batch_norm.bias) def forward(self, x): out = self.conv(x) out = self.batch_norm(out) out = torch.relu(out) return out + x

因为我们计划生成一个深度模型,所以在ResBlock里添加了批归一化层,以防止梯度消失。现在我们向构建一个100个块的层,不需要复制粘贴,而是在__init__()中创建一个实例化的nn.Sequential

class NetResDeep(nn.Module): def __init__(self, n_chans1=32, n_blocks=10): super().__init__() self.n_chans1 = n_chans1 self.conv1 = nn.Conv2d(3, n_chans1, kernel_size=3, padding=1) self.resblocks = nn.Sequential(*(n_blocks * [ResBlock(n_chans=n_chans1)])) self.fc1 = nn.Linear(8 * 8 * n_chans1, 32) self.fc2 = nn.Linear(32, 2) def forward(self, x): out = F.max_pool2d(torch.relu(self.conv1(x)), 2) out = self.resblocks(out) out = F.max_pool2d(out, 2) out = out.view(-1, 8 * 8 * self.n_chans1) out = torch.relu(self.fc1(out)) out = self.fc2(out) return out

上面这样一个如此深的模型不建议在32×32这样的数据集上使用,但至少我们掌握了如何利用PyTorch构建深度模型的基本方法

最后书指出的是,神经网络结构在以非常快的速度演变,但是这本书的目的在于利用PyTorch将模型真正实现。

本章小结如下,摘自Page.202:

卷积可以作为处理图像的前馈网络的线性运算,因为其局部性和平移不变性,使用卷积产生的参数量更少;将多个卷积层、池化层、激活函数叠加,可以将卷积应用于越来越小的特征图像,从而有效的解释随着深度的增加,输入图像中更加宽大的空间上的关系;任何nn.Module子类可以递归的收集和返回其极其子类的参数,此方法可用于计算它们的数量、提供给优化器或检查它们的值;函数式API提供不依赖于存储内部状态的模块,它不保存参数,不会经过训练;一旦训练好,模型的参数就可以保存在磁盘上,然后一行代码就可以重新加载。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3